/*
 * Group 3, User Interface Programming II (vt11)
 * Pooya | Flora | Monica | Can
 */
package vical.view.tutorial;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

/**
 * The class to deal with Glass Tutorial
 * 
 * @author Can
 */
public abstract class GlassTutorial extends JComponent implements MouseListener,
        MouseMotionListener, FocusListener, KeyListener, ComponentListener {

    private Toolkit toolkit;
    private JMenuBar menuBar;
    private Container contentPane;
    protected Component liveComponent;
    private Point mousePoint;
    private Point nearestComponentPoint;
    private Color backgroundColor = new Color(0x88000000, true);
    private Color componentBorderColor = Color.RED;
    // TODO: language
    private String escapeInstruction = "Press 'Escape' to Exit. Press 'F1' back to Tutorial Guide";
    private Color escapeInstructionTextColor = Color.BLUE;
    private Color foreverLineColor = Color.red;
    protected String tutorialTitle;
    private static final int fontSizeTitle = 20;
    private static final int instructionArcWidth = 20;
    private static final int instructionArcHeight = 20;
    protected String instructions;
    private static final int fontSizeInstruction = 15;

    public GlassTutorial(Component liveComponent, JMenuBar menuBar, Container contentPane) {
        this.toolkit = Toolkit.getDefaultToolkit();
        this.menuBar = menuBar;
        this.contentPane = contentPane;
        this.liveComponent = liveComponent;

        this.addMouseListener(this);
        this.addMouseMotionListener(this);
        this.addFocusListener(this);
        this.addKeyListener(this);
        this.addComponentListener(this);
    }

    // get the center point of component in glasspane
    private Point getCenterPoint(Component comp) {
        return SwingUtilities.convertPoint(comp, comp.getWidth() / 2, comp.getHeight() / 2, this);
    }

    protected Point getPoint(Point pointInContentPane) {
        return SwingUtilities.convertPoint(contentPane, pointInContentPane, this);
    }

    protected Point getPoint(Container container, Point pointInContentPane) {
        return SwingUtilities.convertPoint(container, pointInContentPane, this);
    }

    // one component has four angles, which means four points
    // this function will return the nearest point to the mousepoint
    private Point getNearestComponentPoint(Component destComp, Point sourcePoint) {
        Point compLocation = SwingUtilities.convertPoint(contentPane, destComp.getLocation(), this);
        int x = ((compLocation.x + destComp.getWidth() / 2) >= sourcePoint.x) ? compLocation.x : compLocation.x + destComp.getWidth();
        int y = ((compLocation.y + destComp.getHeight() / 2) >= sourcePoint.y) ? compLocation.y : compLocation.y + destComp.getHeight();
        if (nearestComponentPoint == null) {
            nearestComponentPoint = new Point(x, y);
        } else {
            nearestComponentPoint.setLocation(x, y);
        }
        return nearestComponentPoint;
    }

    private void drawLine(Graphics g, Point source, Point dest) {
        g.drawLine(source.x, source.y, dest.x, dest.y);
    }

    private void drawLineWithArrow(Graphics g, Point source, Point dest) {
        drawLine(g, source, dest);
        Point leftArrowPoint = new Point(dest);
        Point rightArrowPoint = new Point(dest);
        drawLine(g, leftArrowPoint, dest);
        drawLine(g, rightArrowPoint, dest);
    }

    private boolean isInComponent(Component comp, Point point) {
        Point compLocation = SwingUtilities.convertPoint(comp.getParent(), comp.getLocation(), this);
        if (compLocation.x <= point.x && (compLocation.x + comp.getWidth()) >= point.x
                && compLocation.y <= point.y && (compLocation.y + comp.getHeight()) >= point.y) {
            return true;
        }
        return false;
    }

    private void fillRect(Graphics g, Rectangle rect) {
        g.fillRect(rect.x, rect.y, rect.width, rect.height);
    }

    private void maskBackground(Graphics g, Component liveComponent) {
        Rectangle drawHere = g.getClipBounds();

        if (liveComponent == null) {
            g.setColor(backgroundColor);
            fillRect(g, drawHere);
            return;
        }

        Point location = liveComponent.getLocation();
        Dimension size = liveComponent.getSize();
        location = SwingUtilities.convertPoint(liveComponent.getParent(), location, this);
        Rectangle safeArea = new Rectangle(location, size);

        if (drawHere.intersects(safeArea)) {
            safeArea = drawHere.intersection(safeArea);
            g.setColor(backgroundColor);
            // top area
            Rectangle tempDrawArea = new Rectangle(drawHere.getLocation());
            tempDrawArea.width = drawHere.width;
            tempDrawArea.height = safeArea.y - drawHere.y;
            fillRect(g, tempDrawArea);
            // left area
            tempDrawArea.x = drawHere.x;
            tempDrawArea.y = safeArea.y;
            tempDrawArea.width = safeArea.x - drawHere.x;
            tempDrawArea.height = safeArea.height;
            fillRect(g, tempDrawArea);
            // right area
            tempDrawArea.x = safeArea.x + safeArea.width;
            tempDrawArea.y = safeArea.y;
            tempDrawArea.width = drawHere.x + drawHere.width - safeArea.x - safeArea.width;
            tempDrawArea.height = safeArea.height;
            fillRect(g, tempDrawArea);
            // bottom
            tempDrawArea.x = drawHere.x;
            tempDrawArea.y = safeArea.y + safeArea.height;
            tempDrawArea.width = drawHere.width;
            tempDrawArea.height = drawHere.y + drawHere.width - tempDrawArea.y;
            fillRect(g, tempDrawArea);
        }
    }

    private void drawPanel(Rectangle rect, Graphics g) {
        g.setColor(new Color(0x88880000, true));
        g.fillRoundRect(rect.x, rect.y, rect.width, rect.height,
                instructionArcWidth, instructionArcHeight);
        g.setColor(new Color(0xaaaa0000, true));
        for (int i = 0; i < 3; i++) {
            g.drawRoundRect(rect.x + i, rect.y + i, rect.width - 2 * i, rect.height - 2 * i,
                    instructionArcWidth, instructionArcHeight);
        }
    }

    private void drawInstructions(Graphics g) {
        Rectangle rect = this.getInstructionPlace();

        // draw panel
        drawPanel(rect, g);

        int y = rect.y + 30;
        int x = rect.x + 10;
        // draw title: the title should not be too long
        if (tutorialTitle != null && !tutorialTitle.isEmpty()) {
            g.setFont(new Font(Font.SANS_SERIF, Font.BOLD, fontSizeTitle));
            g.setColor(Color.YELLOW);
            g.drawString(tutorialTitle, x, y);
            y += 40;
        }

        // draw instructions, the total string is divided line by line
        if (instructions != null && !instructions.isEmpty()) {
            g.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, fontSizeInstruction));
            g.setColor(Color.YELLOW);
            drawStringInMultiplyLines(instructions, g, x, y);
        }
    }

    private void drawStringInMultiplyLines(String str, Graphics g, int x, int y) {
        int startIndex = 0;
        int endIndex = str.indexOf((int) '\n', startIndex);
        int row = 0;
        while (endIndex != -1) {
            String text = str.substring(startIndex, endIndex);
            g.drawString(text, x, y + row * 28);
            row++;
            startIndex = endIndex + 1;
            endIndex = str.indexOf((int) '\n', startIndex);
        }
        g.drawString(str.substring(startIndex), x, y + row * 28);
    }

    private void drawStringInMultiplyLines(String str, Graphics g, int x, int y, int width) {
        final int maxCharCountPerRow = width * 2 / (fontSizeInstruction - 2);
        int charCountPerRow = maxCharCountPerRow;
        int startIndex = 0;
        int row = 0;
        while (startIndex < str.length()) {
            if (startIndex + charCountPerRow >= str.length()) {
                charCountPerRow = str.length() - startIndex;
            }
            String text = str.substring(startIndex, startIndex + charCountPerRow);
            char endChar = text.charAt(text.length() - 1);
            if (startIndex + charCountPerRow < str.length()) {
                char nextChar = str.charAt(startIndex + charCountPerRow);
                // not finished, judge the next char
            }
            String endChars = " ,.?;:";
            if (endChars.indexOf(endChar) == -1 && charCountPerRow == maxCharCountPerRow) {
                text += "-";
            }
            g.drawString(text, x, y + row * 28);
            row++;
            startIndex += charCountPerRow;
        }
    }

    // might be overridden by children
    protected Rectangle getInstructionPlace() {
        Rectangle rect = new Rectangle();
        rect.x = 200;
        rect.y = 100;
        rect.width = 400;
        rect.height = 200;
        return rect;
    }

    @Override
    protected void paintComponent(Graphics g) {

        this.maskBackground(g, liveComponent);

        // show exit instruction
        if (this.mousePoint != null) {
            g.setColor(this.escapeInstructionTextColor);
            g.drawString(escapeInstruction, mousePoint.x, mousePoint.y);
        }

        // show arrows to liveComponet
        if (this.mousePoint != null && this.liveComponent != null && !this.isInComponent(liveComponent, mousePoint)) {
            g.setColor(this.foreverLineColor);
            //this.drawLineWithArrow(g, mousePoint, this.getNearestComponentPoint(liveComponent, mousePoint));
        }

        // highlight the liveComponent
        if (this.liveComponent != null) {
            Point p = liveComponent.getLocation();
            Point containerPoint = SwingUtilities.convertPoint(liveComponent.getParent(),
                    p, this);
            g.setColor(this.componentBorderColor);
            g.drawRect(containerPoint.x, containerPoint.y,
                    liveComponent.getWidth(), liveComponent.getHeight());
        }

        // draw instructions
        if (this.instructions != null && !this.instructions.isEmpty()) {
            this.drawInstructions(g);
        }
    }

    public void mouseClicked(MouseEvent e) {
        redispatchMouseEvent(e, false);
    }

    public void mousePressed(MouseEvent e) {
        redispatchMouseEvent(e, false);
    }

    public void mouseReleased(MouseEvent e) {
        redispatchMouseEvent(e, false);
    }

    public void mouseEntered(MouseEvent e) {
        redispatchMouseEvent(e, false);
    }

    public void mouseExited(MouseEvent e) {
        redispatchMouseEvent(e, false);
    }

    public void mouseDragged(MouseEvent e) {
        redispatchMouseEvent(e, false);
    }

    public void mouseMoved(MouseEvent e) {
        redispatchMouseEvent(e, true);
    }

    public void focusGained(FocusEvent e) {
    }

    public void focusLost(FocusEvent e) {
        if (isVisible() && isFocusable()) {
            //requestFocus();
        }
    }

    protected void instructionPanelClicked() {
    }

    // A basic implementation of redispatching events.
    private void redispatchMouseEvent(MouseEvent e, boolean repaint) {
        this.mousePoint = e.getPoint();
        Rectangle rect = this.getInstructionPlace();
        if (rect.contains(mousePoint) && e.getClickCount() >= 1 && e.getID() == MouseEvent.MOUSE_PRESSED) {
            this.instructionPanelClicked();
        }
        Container container = contentPane;
        Point containerPoint = SwingUtilities.convertPoint(this,
                mousePoint, contentPane);
        if (containerPoint.y < 0) { // we're not in the content pane
            if (containerPoint.y + menuBar.getHeight() >= 0) {
                // The mouse event is over the menu bar.
                // Could handle specially.
            } else {
                // The mouse event is over non-system window
                // decorations, such as the ones provided by
                // the Java look and feel.
                // Could handle specially.
            }
        } else if (liveComponent != null) {
            // The mouse event is probably over the content pane.
            // Find out exactly which component it's over.
            Component component = SwingUtilities.getDeepestComponentAt(container,
                    containerPoint.x, containerPoint.y);

            Point location = SwingUtilities.convertPoint(liveComponent.getParent(), liveComponent.getLocation(), contentPane);
            Rectangle rectLiveComponent = new Rectangle(location, liveComponent.getSize());

            if (((component != null) && (component.equals(liveComponent)))
                    || rectLiveComponent.contains(containerPoint)) {
                // Forward events over the component.
                this.mouseEventOnLiveComponent(mousePoint, component, e);
            } else {
                this.requestFocus();
            }
        }

        // Update the glass pane if requested.
        if (repaint) {
            this.repaint();
        }
    }

    // might be overridden
    protected void mouseEventOnLiveComponent(Point mousePoint, Component component, MouseEvent e) {
        Point componentPoint = SwingUtilities.convertPoint(this,
                mousePoint, component);
        component.dispatchEvent(
                new MouseEvent(component, e.getID(), e.getWhen(), e.getModifiers(),
                componentPoint.x, componentPoint.y, e.getClickCount(), e.isPopupTrigger()));
    }

    public void keyTyped(KeyEvent e) {
    }

    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
            this.setVisible(false);
        }
    }

    public void keyReleased(KeyEvent e) {
    }

    public void componentShown(ComponentEvent e) {
        this.setFocusable(false);
    }

    public void componentResized(ComponentEvent e) {
    }

    public void componentMoved(ComponentEvent e) {
    }

    public void componentHidden(ComponentEvent e) {
    }
}
